﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;

// SIRP Experimental Firmware
// MenuSystem - MenuSystem class and MenuItem objects
// (c) 2014 Chris Burri. MIT License applies. 
// See http://niston.wordpress.com

namespace sdd.subsystems.ui
{
    public class MenuSystem : IEnumerable<MenuItem>
    {
        private Dictionary<string, MenuItem> _MenuItems;

        public event EventHandler InitializeMenuSystem;
           
        public MenuSystem()
        {
            _MenuItems = new Dictionary<string, MenuItem>();
        }

        ~MenuSystem()
        {
            Clear();
        }

        public MenuItem Menu(string itemName)
        {
            return _MenuItems[itemName];
        }

        public void Register(MenuItem newItem)
        {
            _MenuItems.Add(newItem.Name, newItem);
        }
        public void Register(MenuItem newItem, MenuItem parentItem)
        {
            _MenuItems[parentItem.Name].SubItems.Add(newItem);
        }

        public void Unregister(MenuItem itemToRemove)
        {
            _MenuItems.Remove(itemToRemove.Name);
        }

        public void Clear()
        {
            // clear backreference
            foreach (var itemsEntry in _MenuItems) { itemsEntry.Value.Parent = null; }

            // clear list
            _MenuItems.Clear();
        }

        public int Count { get { return _MenuItems.Count(); } }

        public IEnumerator<MenuItem> GetEnumerator()
        {
            return _MenuItems.Values.GetEnumerator();
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }

    // basic menu item - has a name but no value; used for top level menus
    public class MenuItem
    {
        public MenuItem Parent { set; get; }
        public string Name { set; get; }
        public List<MenuItem> SubItems { set; get; }
        public bool Selected { get; set; }
        public MenuItem(string itemName)
        {
            SubItems = new List<MenuItem>();
            Name = itemName;
        }
    }

    // link menu item - navigates to the linked screen (IScreen implementation)
    public class LinkMenuItem : MenuItem
    {
        public IScreen LinkedScreen { set; get; }
        public LinkMenuItem(string itemName, IScreen linkedScreen) : base(itemName)
        {
            LinkedScreen = linkedScreen;
        }
    }

    // clickable menu items can be "clicked" with the center button and raise a click event
    public interface IClickableMenuItem
    {
        void Click();
    }

    // menu item can be increased/decreased
    public interface ICountableMenuItem
    {
        void Increase(bool largeStep = false);
        void Decrease(bool largeStep = false);
    }
    
    // menu item can be toggled between states
    public interface IToggleableMenutItem
    {
        void Toggle();
    }

    // menu item can be browsed (next/previous)
    public interface IBrowseableMenuItem
    {
        void Next();
        void Previous();
    }

    // menu item can be printed (on LCD/UI)
    public interface IPrintableValueMenuItem
    {
        string Print();
    }

    // event args for item click
    public class MenuItemClickEventArgs<T> : EventArgs
    {
        public MenuItemClickEventArgs(T clickedValue)
        {
            ClickedValue = clickedValue;
        }
        public T ClickedValue { get; private set; }
    }

    // observable menu items provide notifications whenever the item value changes
    public abstract class ObservableMenuItem<T> : MenuItem
    {
        protected ObservableMenuItem(string itemName) : base(itemName) { }
        public abstract event EventHandler<ObservableMenuItemValueChangedEventArgs<T>> ValueChanged;
    }

    // event args for observable menu item value change (generic type)
    public class ObservableMenuItemValueChangedEventArgs<T> : EventArgs 
    {
        public ObservableMenuItemValueChangedEventArgs(T oldValue, T newValue)
        {
            OldValue = oldValue;
            NewValue = newValue;
        }
        public T OldValue { get; private set; }
        public T NewValue { get; private set; }
    }

    // boolean menu item - can be either enabled (true) or disabled (false)
    public class SwitchMenuItem : ObservableMenuItem<bool>, IToggleableMenutItem, IPrintableValueMenuItem
    {
        private bool _Value;

        public override event EventHandler<ObservableMenuItemValueChangedEventArgs<bool>> ValueChanged;
        
        public SwitchMenuItem(string itemName, bool initValue) : base(itemName)
        {
            Value = initValue;
        }
        
        public bool Value
        {
            get
            {
                return _Value;
            }
            set
            {
                bool oldValue = _Value;
                _Value = value;
                if (ValueChanged != null) { ValueChanged(this, new ObservableMenuItemValueChangedEventArgs<bool>(oldValue, _Value)); }
            }
        }

        public void Toggle()
        {
            Value = !Value;
        }

        public string Print()
        {
            return (_Value ? "Enabled" : "Disabled");
        }
    }
    
    // integer menu item - can be set to any integer value between min and max
    public class IntMenuItem : ObservableMenuItem<int>, ICountableMenuItem, IPrintableValueMenuItem
    {
        private int _Value;
        private object calcLock = new Object();

        public override event EventHandler<ObservableMenuItemValueChangedEventArgs<int>> ValueChanged;
        
        public int Min { set; get; }
        public int Max { set; get; }
        public int Step { set; get; }
        public int StepLarge { set; get; }
        public string Unit { set; get; }

        public int Value
        {
            get { return _Value; }
            set
            {
                if (value > Max | value < Min) { throw new ArgumentOutOfRangeException(); }
                int oldValue = _Value;
                _Value = value;
                if (ValueChanged != null) { ValueChanged(this, new ObservableMenuItemValueChangedEventArgs<int>(oldValue, _Value)); }
            }
        }

        public IntMenuItem(string itemName, int initValue, int minValue, int maxValue, int step = 1, int stepLarge = 10) : base(itemName)
        {
            Min = minValue;
            Max = maxValue;
            Step = step;
            StepLarge = stepLarge;
            Value = initValue;
        }

        public void Increase(bool stepLarge = false)
        {
            lock (calcLock)
            {
                if (stepLarge) { Value = Value + StepLarge; } else { Value = Value + Step; }
            }
        }

        public void Decrease(bool stepLarge = false)
        {
            lock (calcLock)
            {
                if (stepLarge) { Value = Value - StepLarge; } else { Value = Value - Step; }
            }
        }

        public string Print()
        {
            return _Value.ToString() + Unit;
        }
    }

    // IP address menu item - holds an IP address
    public class IPAddressMenuItem : ObservableMenuItem<IPAddress>, IPrintableValueMenuItem
    {
        private IPAddress _Value;

        public override event EventHandler<ObservableMenuItemValueChangedEventArgs<IPAddress>> ValueChanged;

        public IPAddressMenuItem(string itemName, IPAddress initValue) : base (itemName) 
        {
            Value = initValue;
        }

        public IPAddress Value
        {
            get { return _Value;  }
            set
            {
                IPAddress oldValue = _Value;
                _Value = value;
                if (ValueChanged != null) { ValueChanged(this, new ObservableMenuItemValueChangedEventArgs<IPAddress>(oldValue, _Value)); }
            }
        }

        public string Print()
        {
            return _Value.ToString();
        }
    }

    // SelectListMenuItem holds a list of possible selections (similar to a combo box in winforms).
    public class SelectListMenuItem<T> : ObservableMenuItem<T>, IClickableMenuItem, IBrowseableMenuItem, IPrintableValueMenuItem
    {
        private Dictionary<string, T> _SelectList;
        private string _SelectionName;

        public override event EventHandler<ObservableMenuItemValueChangedEventArgs<T>> ValueChanged;
        public event EventHandler<MenuItemClickEventArgs<T>> ItemClick;

        public T SelectionValue { private set; get; }

        public string SelectionName 
        {  
            get { return _SelectionName; }
            set
            {
                if (value != _SelectionName)
                {
                    T oldValue = SelectionValue;
                    _SelectionName = value;
                    SelectionValue = _SelectList[_SelectionName];
                    if (ValueChanged != null) { ValueChanged(this, new ObservableMenuItemValueChangedEventArgs<T>(oldValue, SelectionValue)); }
                }
            }
        }

        public SelectListMenuItem(string itemName, Dictionary<string, T> selectionList, string defaultSelection) : base (itemName)
        {
            _SelectList = selectionList;
            SelectionName = defaultSelection;
            SelectionValue = _SelectList[SelectionName];
        }

        public void Next()
        {
            int curSelIndex = GetSelectionIndex(SelectionName);
            if (curSelIndex > -1)
            {
                string[] selectionNames = _SelectList.Keys.ToArray<string>();
                if (curSelIndex + 1 < _SelectList.Count)
                {
                    SelectionName = selectionNames[curSelIndex + 1];
                }
                else
                {
                    SelectionName = selectionNames[0];
                }
            }
        }

        public void Previous()
        {
            int curSelIndex = GetSelectionIndex(SelectionName);
            if (curSelIndex > -1)
            {
                string[] selectionNames = _SelectList.Keys.ToArray<string>();
                if (curSelIndex - 1 >= 0)
                {
                    SelectionName = selectionNames[curSelIndex - 1];
                }
                else
                {
                    SelectionName = selectionNames[selectionNames.GetUpperBound(0)];
                }
            }
        }

        public void Click()
        {
            if (ItemClick != null) { ItemClick(this, new MenuItemClickEventArgs<T>(SelectionValue)); }
        }


        private int GetSelectionIndex(string selectionName)
        {
            int i = 0;
            foreach (string selection in _SelectList.Keys)
            {
                if (selection == selectionName)
                {
                    return i;
                }
                i++;
            }
            return -1;
        }

        public string Print()
        {
            return SelectionName;
        }
    }

    // Generic menu item holds any value
    public class GenericMenuItem<T> : ObservableMenuItem<T>, IPrintableValueMenuItem
    {
        private T _Value;

        public override event EventHandler<ObservableMenuItemValueChangedEventArgs<T>> ValueChanged;

        public GenericMenuItem(string itemName, T initValue) : base (itemName)
        {
            Value = initValue;
        }
        
        public T Value
        {
            get { return _Value; }
            set
            {
                T oldValue = _Value;
                _Value = value;
                if (ValueChanged != null) { ValueChanged(this, new ObservableMenuItemValueChangedEventArgs<T>(oldValue, _Value)); }
            }
        }

        public string Print()
        {
            return _Value.ToString();
        }
    }
}
